package com.terrynoya.fight.evaluation
{
	import com.terrynoya.fight.char.Character;
	import com.terrynoya.fight.emuns.Operator;
	import com.terrynoya.fight.evaluation.token.CustomFunctionData;
	import com.terrynoya.fight.evaluation.token.NumberData;
	import com.terrynoya.fight.evaluation.token.RangeData;
	
	import org.as3commons.reflect.Type;

	public class ExpressionInterpretor
	{
		public function ExpressionInterpretor()
		{
			
		}
		
		public function excute(node:Node,data:Object):*
		{
//			EvaluationSystem.printNode(node);
			var rlt:* = this.exp(node,data);
//			trace("excute: ",rlt);
			return rlt;
		}
		
		private function exp(node:Node,data:Object):*
		{
			var lhs:*;
			var rhs:*;
			if(this.isLogicNode(node))
			{
				lhs	= this.exp(node.getChildAt(0),data);	
				rhs = this.exp(node.getChildAt(1),data);
				return this.logic(node.value,lhs,rhs);
			}
			else if(this.isCompareNode(node))
			{
				lhs = this.exp(node.getChildAt(0),data);	
				rhs = this.exp(node.getChildAt(1),data);
				return this.compare(node.value,lhs,rhs);
			}
			else if(this.isRange(node))
			{
				return this.range(node,data);
			}
			else if(this.isCustomFunc(node))
			{
				return this.excuteCustomFun(node,data);
			}
			else if(this.isUnaryOper(node))
			{
				lhs = this.exp(node.getChildAt(0),data);
				return !lhs;
			}
			else if(this.isMathOper(node))
			{
				if(node.childLength == 1)
				{
					lhs = this.exp(node.getChildAt(0),data);
					return this.unaryMath(node.value,lhs);
				}
				else
				{
					lhs = this.exp(node.getChildAt(0),data);
					rhs = this.exp(node.getChildAt(1),data);
					return this.math(node.value,lhs,rhs);
				}
			}
			else if(this.isNumberNode(node))
			{
				return Number(node.value);
			}
			else
			{
				return 0;
			}
		}
		
		private function range(node:Node,data:Object):*
		{
			var lhs:* = this.exp(node.getChildAt(0),data);
			var rangeLeft:* = this.excute(node.getChildAt(1),data);
			var rangeRight:* = this.excute(node.getChildAt(2),data);
			var equalOper:int = node.arguments[0];
			
			var greaterOper:String = this.getGeaterOper(node.arguments[1]);
			var lessOper:String = this.getLessOper(node.arguments[2]);
			
			var rangeLeftRlt:Boolean =  this.compare(greaterOper,lhs,rangeLeft);
			var rangeRightRlt:Boolean =  this.compare(lessOper,lhs,rangeRight);
			
			var rlt:Boolean = rangeLeftRlt && rangeRightRlt; 
			
			if(equalOper == Operator.Equals)
			{
				return rlt; 
			}
			else if(equalOper == Operator.NotEquals)
			{
				return !rlt;
			}
			return false;
		}
		
		private function getGeaterOper(txt:String):String
		{
			if(txt == "[")
			{
				return ">=";
			}
			else
			{
				return ">";
			}
		}
		
		private function getLessOper(txt:String):String
		{
			if(txt == "]")
			{
				return "<=";
			}
			else
			{
				return "<";
			}
		}
		
		private function excuteCustomFun(node:Node,data:Object):*
		{
			var tokenData:CustomFunctionData = node.token.data as CustomFunctionData;
			var type:Type = tokenData.type;
			var rlt:* = type.getMethod("eval").invoke(type.clazz,[data,node]);
//			trace("customfun",node.value," rlt:",rlt)
			return rlt;
		}
		
		private function logic(oper:String,lhs:*,rhs:*):Boolean
		{
			var rlt:Boolean = false;
			switch(oper)
			{
				case "&&":
				{
					rlt = lhs && rhs;
					break;
				}
				case "||":
				{
					rlt = lhs || rhs;
					break;
				}
			}
			return rlt;
		}
		
		private function unaryMath(oper:String,lhs:*):*
		{
			var rlt:Number = 0;
			switch(oper)
			{
				case "+":
				{
					rlt = lhs;
					break;
				}
				case "-":
				{
					rlt = -lhs;
					break;
				}
			}
			return rlt;
		}
		
		private function math(oper:String,lhs:*,rhs:*):Number
		{
			var rlt:Number = 0;
			switch(oper)
			{
				case "+":
				{
					rlt = lhs + rhs;
					break;
				}
				case "-":
				{
					rlt = lhs - rhs;
					break;
				}
				case "*":
				{
					rlt = lhs * rhs;
					break;
				}
				case "/":
				{
					rlt = lhs / rhs;
					break;
				}
				case "%":
				{
					rlt = lhs % rhs;
					break;
				}
			}
			return rlt;
		}
		
		private function compare(oper:String,lhs:*,rhs:*):Boolean
		{
			var rlt:Boolean = false;
			switch(oper)
			{
				case "=":
				{
					rlt = lhs == rhs;
					break;
				}
				case ">":
				{
					rlt = lhs > rhs;
					break;
				}
				case ">=":
				{
					rlt = lhs >= rhs;
					break;
				}
				case "<":
				{
					rlt = lhs < rhs;
					break;
				}
				case "<=":
				{
					rlt = lhs <= rhs;
					break;
				}
				case "!=":
				{
					rlt = lhs != rhs;
					break;
				}
			}
			return rlt;
		}
		
		private function isNumberNode(node:Node):Boolean
		{
			var data:NumberData = node.token.data as NumberData;
			return data != null
		}
		
		private function isCustomFunc(node:Node):Boolean
		{
			var data:CustomFunctionData = node.token.data as CustomFunctionData;
			return data != null; 
		}
		
		private function isLogicNode(value:Node):Boolean
		{
			var txt:String = value.token.text;
			var rlt:Boolean = txt == "&&" || txt == "||";
			return rlt;
		}
		
		private function isCompareNode(value:Node):Boolean
		{
			var txt:String = value.token.text;
			var rlt:Boolean = txt == "<" || txt == "<=" || txt == ">" || txt == ">=" || txt == "=" || txt == "!=";
			return rlt;
		}
		
		private function isUnaryOper(value:Node):Boolean
		{
			var txt:String = value.token.text;
			var rlt:Boolean = txt == "!" || txt == "~";
			return rlt;
		}
		
		private function isMathOper(value:Node):Boolean
		{
			var txt:String = value.token.text;
			var rlt:Boolean = txt == "+" || txt == "-" || txt == "*" || txt == "/" || txt == "%";
			return rlt;
		}
		
		private function isRange(value:Node):Boolean
		{
			var data:RangeData = value.token.data as RangeData;
			return data != null;
		}
	}
}